package com.cd.university.aop;

import com.cd.university.annotation.OperationLogAnno;
import com.cd.university.common.utils.IpUtils;
import com.cd.university.db.mapper.OperationLogMapper;
import com.cd.university.db.pojo.OperationLog;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 日志切面类
 * @author 陈栋
 * @create 2021/11/7 19:31
 */
@Aspect
@Component("operationLogAspect")
public class OperationLogAspect {

    @Autowired
    private OperationLogMapper operationLogMapper;

    private static final Logger log = LoggerFactory.getLogger(OperationLogAspect.class);

    // 配置织入点(以OperationLog注解为标志),捕获controller下面所有的类的所有的方法，包括controller的子包所有的类的所有的方法
    @Pointcut("execution(* com.cd.university.controller..*.*(..))")
    public void logPointCut() {
    }

    /**
     * 后置通知 用于拦截操作，在方法返回后执行
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()")
    public void doAfterReturn(JoinPoint joinPoint) {
        handleLog(joinPoint, null);
    }

    /**
     * 拦截异常操作，有异常时执行
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e);
    }

    private void handleLog(JoinPoint joinPoint, Exception e) {

        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        // 获得请求的Url
        StringBuffer requestURL = request.getRequestURL();
        String requestMethod = request.getMethod();

        // 获得Ip地址
        String ipAddress = IpUtils.getIPAddress(request);

        // 访问类名
        String className = joinPoint.getTarget().getClass().getSimpleName();
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取方法名称
        String methodName = method.getName();
        // 获得方法的参数类型
        Class<?>[] methodParameterTypes = method.getParameterTypes();

        StringBuffer parameters = new StringBuffer();
        for (Class<?> parameterType : methodParameterTypes) {
            if (parameters.length()==0) {
                parameters.append(parameterType.getSimpleName() + ".class");
            }else {
                parameters.append("," + parameterType.getSimpleName() + ".class");
            }
        }
        String parameterTypes = parameters.toString();

        // 获得注解中的参数的内容
        ApiOperation annotation = method.getAnnotation(ApiOperation.class);
        String describe = null;

        // 如果annotation注解为空，说明该用户未登录
        if (annotation==null) {
            describe = "用户登陆失败或未登录";
        }else {
            describe = annotation.value();
        }
        // 向数据库中写入数据
        insertOperationLog(className,methodName,parameterTypes,requestURL.toString(),ipAddress,describe);
    }

    /**
     * 异步插入数据库，不影响系统性能
     * @param className 类名
     * @param methodName 方法名
     * @param parameterTypes 方法参数
     * @param requestUrl 请求的url
     * @param ip ip地址
     * @param describe 方法描述
     */
    @Async
    public void insertOperationLog(String className,
                                    String methodName,
                                    String parameterTypes,
                                    String requestUrl,
                                    String ip,
                                    String describe) {
        Date operationTime = new java.sql.Date(System.currentTimeMillis());
        // 生成一个操作信息类
        OperationLog operationLog = new OperationLog(operationTime, className, methodName, parameterTypes, requestUrl, ip, describe);
        // 将操作信息插入数据库
        operationLogMapper.insertSelective(operationLog);
    }
}
